/**
 * @author Stephan Halbeisen, Kaufmann Daniel
 *
 * 20.04.2011
 * DomainController.java
 */

/**
 * information:
 * - handles the save operations for the application (checking and calls databaseController for saving)
 * - is a Singelton
 */
package at.fhv.easyDietWeb.server.controller;

import java.util.Date;
import java.util.List;

import at.fhv.easyDietWeb.shared.db.diet.Contact;
import at.fhv.easyDietWeb.shared.db.diet.DietaryParameter;
import at.fhv.easyDietWeb.shared.db.diet.LabResult;
import at.fhv.easyDietWeb.shared.db.diet.Meal;
import at.fhv.easyDietWeb.shared.db.diet.MealLine;
import at.fhv.easyDietWeb.shared.db.diet.MealType;
import at.fhv.easyDietWeb.shared.db.diet.NutritionalJournal;
import at.fhv.easyDietWeb.shared.db.diet.PatientState;
import at.fhv.easyDietWeb.shared.db.diet.Plan;
import at.fhv.easyDietWeb.shared.db.masterData.Patient;
import at.fhv.easyDietWeb.shared.exception.LabResultSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.database.MissingSearchKeyException;
import at.fhv.easyDietWeb.shared.exception.database.NoSearchResultException;
import at.fhv.easyDietWeb.shared.exception.database.SaveFailedException;
import at.fhv.easyDietWeb.shared.exception.database.UpdateFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.ContactSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.DietaryParameterSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.InvalidInputException;
import at.fhv.easyDietWeb.shared.exception.domain.MealLineSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.MealSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.MealTypeAlreadyExistsException;
import at.fhv.easyDietWeb.shared.exception.domain.MealTypeSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.MissingTherapyException;
import at.fhv.easyDietWeb.shared.exception.domain.NoPlanForTimespanException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientExistsException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientHasNoPlanException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientHasNoTherapyException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientStateSaveFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.PatientUpdateFailedException;
import at.fhv.easyDietWeb.shared.exception.domain.PlanSaveFailedException;

/**
 * The DomainController interacts between the use case layer and the persistence
 * layer. Database calls which need further tests (i.e. a patient has a name)
 * invoke this class. If every test was successful, DatabaseController will be
 * invoked.
 * 
 * @author Philipp
 * 
 */
public class DomainController
{

	private DatabaseController m_dbC = DatabaseController.getInstance();
	private static DomainController _instance = null;

	private DomainController()
	{
	}

	public static DomainController getInstance()
	{
		if (_instance == null)
		{
			_instance = new DomainController();
		}
		return _instance;
	}

	/**
	 * First checks and afterwards calls the "saveDietaryParameter" method in
	 * DatabaseController, if all mandatory fields have been filled correctly
	 * else a "invalidInputException" will be thrown
	 * 
	 * @param params
	 *            dietary parameters to be saved
	 * @throws InvalidInputException
	 */
	public void saveDietaryParameters(List<DietaryParameter> params)
			throws InvalidInputException, DietaryParameterSaveFailedException
	{
		boolean paramsCorrect = true; // as long true as no parameter failed the
										// check

		for (DietaryParameter dp : params)
		{
			if (paramsCorrect)
			{
				paramsCorrect = checkDietaryParameterParams((DietaryParameter) dp);
			}
		}

		if (paramsCorrect == false) // if a parameter has failed the check
			throw new InvalidInputException();
		else
		{
			for (DietaryParameter dp : params) // saves all params from the
													// list to the database
			{
				try
				{
					m_dbC.saveDietaryParameter((DietaryParameter) dp);
				} catch (SaveFailedException ex)
				{
					throw new DietaryParameterSaveFailedException();
				}
			}
		}
	}

	/**
	 * function to check the DietaryParameter-Parameters from the
	 * saveDietaryParameter method
	 * 
	 * @param dp
	 * @throws InvalidInputException
	 */
	public boolean checkDietaryParameterParams(DietaryParameter dp)
	{
		if ((dp.getIsTargetParameter() == true || dp.getIsTargetParameter() == false)
				&& (dp.getNutritionalValueName() != null || dp.getWhoParams() != null))
			return true;
		else
			return false;
	}

	/**
	 * checks if all mandatory fields are filled and saves afterwards the plan
	 * on the database
	 * 
	 * @param plan
	 *            the plan to be saved
	 * @throws InvalidInputException
	 *             if one or more fields are not filled (correctly)
	 */
	public void savePlan(Plan plan) throws InvalidInputException,
			PlanSaveFailedException
	{
		if (plan.getType() == null || plan.getStartDate() == null
				|| plan.getEndDate() == null || plan.getTherapy() == null)
		{
			throw new InvalidInputException();
		} else
		{
			try
			{
				m_dbC.savePlan(plan);
			} catch (SaveFailedException e)
			{
				throw new PlanSaveFailedException();
			}
		}
	}

	/**
	 * checks if all mandatory fields are filled and saves afterwards the
	 * nutritionalJournal on the database
	 * 
	 * @param nutritionalJournal
	 *            the plan to be saved
	 * @throws InvalidInputException
	 *             if one or more fields are not filled (correctly)
	 */
	public void saveNutritionalJournal(NutritionalJournal nutritionalJournal, List<Meal> meals, List<MealLine> mealLines)
			throws InvalidInputException, PlanSaveFailedException
	{
		if (nutritionalJournal.getStartDate() == null
				|| nutritionalJournal.getEndDate() == null
				|| nutritionalJournal.getPatient() == null)
		{
			throw new InvalidInputException();
		} else {
			try {
				m_dbC.saveNutritionalJournal(nutritionalJournal, meals, mealLines);
			} catch (SaveFailedException e) {
				throw new PlanSaveFailedException();
			}
		}
	}

	/**
	 * checks if all mandatory fields are filled and saves afterwards the meal
	 * on the database
	 * 
	 * @param meals
	 *            the meals to be saved
	 * @throws InvalidInputException
	 *             if one or more fields are not filled (correctly)
	 */
	public void saveMeal(List<Meal> meals) throws InvalidInputException,
			MealSaveFailedException
	{
		for (Meal m : meals)
		{
			if (m.getPlan() == null || m.getDay() == null)
			{
				throw new InvalidInputException();
			}
		}

		for (Meal m : meals)
		{
			try
			{
				m_dbC.saveMeal(m);
			} catch (SaveFailedException ex)
			{
				throw new MealSaveFailedException();
			}
		}
	}

	/**
	 * checks if all mandatory fields are filled and saves afterwards the
	 * mealLine on the database
	 * 
	 * @param mealLines
	 *            list of the MealLines to be saved
	 * @throws InvalidInputException
	 *             if one or more fields are not filled (correctly)
	 */
	public void saveMealLine(List<MealLine> mealLines)
			throws InvalidInputException, MealLineSaveFailedException
	{
		for (MealLine ml : mealLines)
		{
			if (ml.getMeal() == null || ml.getPortion() <= 0)
			{
				throw new InvalidInputException();
			}
		}

		for (MealLine ml : mealLines)
		{
			try
			{
				m_dbC.saveMealLine(ml);
			} catch (SaveFailedException ex)
			{
				throw new MealLineSaveFailedException();
			}
		}
	}

	/**
	 * checks the mandatory fields (needed data) and saves the mealType to the
	 * database if it does not already exist.
	 * 
	 * @param mealType
	 *            the meal type to be saved
	 * @throws InvalidInputException
	 *             , if a mandatory field is not filled (correctly).
	 */
	public void saveMealType(MealType mealType)
			throws MealTypeAlreadyExistsException, MealTypeSaveFailedException
	{
		List<MealType> mtList;

		try
		{
			mtList = m_dbC.getMealTypes();

			for (MealType mt : mtList)
			{
				if (mt.getDescription().equalsIgnoreCase(
						mealType.getDescription()))
				{
					throw new MealTypeAlreadyExistsException();
				}
			}
		} catch (NoSearchResultException ex)
		{
			// error might be no databaseConnection. we do not assume this can
			// happen ;)
		}

		try
		{
			m_dbC.saveMealType(mealType);
		} catch (SaveFailedException ex)
		{
			throw new MealTypeSaveFailedException();
		}
	}

	/**
	 * checks if the patient can be persisted on the database, and calls
	 * therefore the DatabaseController.savePatient method.
	 * 
	 * @param patient
	 *            the patient to be saved
	 * @throws InvalidInputException
	 * @throws PatientSaveFailedException
	 * @throws PatientExistsException
	 */
	public void savePatient(Patient patient) throws InvalidInputException, PatientSaveFailedException, PatientExistsException {
		if (!checkPatient(patient)) {
			throw new InvalidInputException();
		} else {
			try {
				m_dbC.getPatient(null, null, patient.getInsurancePolicyNumber());
				throw new PatientSaveFailedException();
			} catch (NoSearchResultException exc) {
				try {
					m_dbC.savePatient((Patient) patient);
					return;
				} catch (SaveFailedException e) {
					throw new PatientSaveFailedException();
				}
			} catch (MissingSearchKeyException exc) {
				return;
			}
		}
	}
	

	/**
	 * checks if all mandatory fields are set (to save this patient)
	 * 
	 * @param patient
	 * @return true if the input is correct
	 */
	private boolean checkPatient(Patient patient)
	{
		if (patient.getPerson() == null)
		{
			return false;
		} else if (patient.getPerson().getFirstName() == null
				|| patient.getPerson().getLastName() == null
				|| patient.getPerson().getAddress() == null)
		{
			return false;
		} else if (patient.getPerson().getAddress().getStreet() == null
				|| patient.getPerson().getAddress().getHouseNumber() == null
				|| patient.getPerson().getAddress().getPostalCode() == null
				|| patient.getPerson().getAddress().getCity() == null
				|| patient.getPerson().getAddress().getCountry() == null)
		{
			return false;
		} else
		{
			return true;
		}
	}

	/**
	 * Makes tests for a contact and saves it afterwards
	 * 
	 * @param contact
	 *            the contact to be saved
	 * @throws PatientHasNoTherapyException
	 * @throws ContactSaveFailedException
	 */
	public void saveContact(Contact contact)
			throws PatientHasNoTherapyException, ContactSaveFailedException
	{
		if (contact.getTherapy() == null)
		{
			throw new PatientHasNoTherapyException();
		} else
		{
			try
			{
				m_dbC.saveContact(contact);
			} catch (SaveFailedException e)
			{
				throw new ContactSaveFailedException();
			}
		}
	}

	/**
	 * Tests and saves the patient state
	 * 
	 * @param patientState
	 *            the patient state to be saved
	 * @throws PatientStateSaveFailedException
	 * @throws InvalidInputException
	 */
	public void savePatientState(PatientState patientState)
			throws PatientStateSaveFailedException, InvalidInputException
	{
		if (patientState.getContact() == null)
		{
			throw new InvalidInputException();
		} else
		{
			try
			{
				m_dbC.savePatientState(patientState);
			} catch (SaveFailedException e)
			{
				throw new PatientStateSaveFailedException();
			}
		}
	}

	/**
	 * Saves a lab result
	 * 
	 * @param labResult
	 * @throws MissingTherapyException
	 * @throws LabResultSaveFailedException
	 */
	public void saveLabResult(LabResult labResult)
			throws MissingTherapyException, LabResultSaveFailedException
	{
		if (labResult.getTherapy() == null)
		{
			throw new MissingTherapyException();
		} else
		{
			try
			{
				DatabaseController.getInstance().saveLabResult(labResult);
			} catch (SaveFailedException e)
			{
				throw new LabResultSaveFailedException();
			}
		}

	}

	/**
	 * updates an existing patient
	 * 
	 * @param patient
	 * @throws InvalidInputException
	 * @throws PatientUpdateFailedException
	 */
	public void updatePatient(Patient patient)
			throws PatientUpdateFailedException
	{
		try
		{
			m_dbC.updatePatient(patient);
		} catch (UpdateFailedException exc)
		{
			throw new PatientUpdateFailedException();
		}
	}

	/**
	 * save a DietaryParameter for a dietPlan. equal to the saveDietaryParameter
	 * method. (with Parameter-check)
	 * 
	 * @param dietaryParameters
	 * @throws InvalidInputException
	 * @throws DietaryParameterSaveFailedException
	 */
	public void saveDietaryParametersForDietPlan(
			List<DietaryParameter> dietaryParameters)
			throws InvalidInputException, DietaryParameterSaveFailedException
	{
		boolean paramsCorrect = true; // as long true as no parameter failed the
										// check

		for (DietaryParameter dp : dietaryParameters)
		{
			if (paramsCorrect)
			{
				paramsCorrect = checkDietaryParameterParams(dp);
			}
		}

		if (paramsCorrect == false) // if a parameter has failed the check
			throw new InvalidInputException();
		else
		{
			for (DietaryParameter dp : dietaryParameters) // saves all
															// parameters from
															// the list to the
															// database
															// (separately)
			{
				try
				{
					m_dbC.saveDietaryParameter(dp);
				} catch (SaveFailedException ex)
				{
					throw new DietaryParameterSaveFailedException();
				}
			}
		}
	}

	/**
	 * searches for a plan for the given parameters
	 * 
	 * @param patient
	 * @param startDate
	 * @param endDate
	 * @return a plan for the given parameters
	 * @throws NoPlanForTimespanException
	 * @throws PatientHasNoPlanException
	 */
	public Plan getPlan(Patient patient, Date startDate, Date endDate)
			throws NoPlanForTimespanException, PatientHasNoPlanException
	{
		try
		{
			List<Plan> plans = DatabaseController.getInstance().getPlans(
					patient);

			for (Plan p : plans)
			{
				if (!startDate.before(p.getStartDate())
						&& !endDate.after(p.getEndDate()))
				{
					return p;
				}
			}

			throw new NoPlanForTimespanException();

		} catch (NoSearchResultException exc)
		{
			throw new PatientHasNoPlanException();
		}

	}
}
